Skip to content

Filter PacBio tumour-only SAVANA SVs against the SV PoN; cap ReConPlot SVs (#225) - #228

Open
ljwharbers wants to merge 6 commits into
devfrom
fix/savana-pb-tumoronly-pon
Open

ljwharbers wants to merge 6 commits into
devfrom
fix/savana-pb-tumoronly-pon

Conversation

@ljwharbers

Copy link
Copy Markdown
Collaborator

Closes #225.

Why RECONPLOT_SAVANA took 6–8 h on PacBio tumour-only

SAVANA's PacBio classifier (savana to --pb → classify_pacbio / pacbio_pass_somatic) only rejects a call on matched-normal evidence. With no normal, every germline SV with enough tumour reads is written to the somatic VCF. ReConPlot then adds one ggplot layer per SV. The same VCF also feeds VEP_SAVANA and Padfoot.

Changes

  • SAVANA_PON_FILTER (bin/savana_pon_filter.py) filters the somatic VCF against the genome's Severus germline SV panel of normals (pon_file). It uses Severus's own matching rule (extract_pon / add_pon) and keeps or drops BND mates together.
    • TUMORONLY_SAVANA sends only PacBio samples through it. ONT (SAVANA's trained tumour-only model) and genomes without a PoN pass through unchanged.
    • Output: <id>.pon_filtered.classified.somatic.vcf plus a summary TSV. The unfiltered VCF is still published.
    • --savana_pb_pon_filter false turns it off.
  • --reconplot_max_svs (default 1000): ReConPlot draws only the best-supported SVs, and the title reads top N of M SVs. The harmonised *.reconplot_sv.tsv table keeps every SV.

Validation

Simulated PacBio tumour-only C8 (hg38): SAVANA's PacBio filter applied to the paired C8 breakpoints with the normal checks removed. The modules ran under Apptainer on one CPU.

Run SVs Result
PoN filter 27,591 → 761 events 1 s
ReConPlot, PoN-filtered 761 21 min, all figures
ReConPlot, unfiltered, --max-svs 1000 1,000 of 27,591 33 min, all figures
ReConPlot, unfiltered, --max-svs 2500 2,500 11 min, genome-wide panel failed (R node stack overflow)
ReConPlot, unfiltered, uncapped 27,591 not finished after 2 h
  • Why the default cap is 1000: BL1 ONT (2,084 SAVANA junctions) already gets no genome-wide figure on dev because of the same overflow.
  • Limitation: by C8's matched normal, all 761 survivors are still germline. Severus's 21 paired-somatic C8 calls lose 16 to the same rule, almost all small repeat-site INS/DEL; the large events are kept. The figures become usable, but PacBio tumour-only SAVANA SVs remain a fallback, and the docs say so.

Tests

  • New module test for SAVANA_PON_FILTER.
  • New stub test for TUMORONLY_SAVANA routing: PacBio is filtered, ONT and no-PoN are not.
  • New RECONPLOT case for --max-svs.
  • RECONPLOT snapshots change only in the versions.yml md5, from the wrapper VERSION bump.
  • All passed under Apptainer on Mindwell.
  • No test samplesheet has a PacBio tumour-only sample, so the pipeline-level snapshots are unaffected.

PR checklist

  • This comment contains a description of changes (with reason).
  • Tests added.
  • Code lints (pre-commit / nextflow lint).
  • Output documentation in docs/output.md is updated.
  • CHANGELOG.md is updated.

Follow-ups, not in this PR:

  • Report the missing tumour-only handling in classify_pacbio upstream.
  • Vectorise ReConPlot's per-SV layers in the container, which would make the cap unnecessary.

🤖 Generated with Claude Code

ljwharbers and others added 2 commits October 29, 2026 11:42
…lot SVs

SAVANA's PacBio classifier (`savana to --pb`) only rejects a call on matched-normal
evidence, so without a normal nearly every germline SV comes out somatic, and
RECONPLOT_SAVANA (one ggplot layer per SV) took 6-8 h (#225). The same VCF feeds
VEP_SAVANA and Padfoot.

- SAVANA_PON_FILTER / bin/savana_pon_filter.py: Severus's own PoN matching rule
  (extract_pon/add_pon) against the genome's pon_file, BND mates kept together.
  TUMORONLY_SAVANA routes PacBio somatic VCFs through it; ONT (SAVANA's TO model)
  and genomes without a PoN pass unchanged. --savana_pb_pon_filter turns it off.
  On a simulated PacBio tumour-only C8 (hg38) it keeps 761 of 27,591 events.
- ReConPlot wrapper --max-svs (--reconplot_max_svs, default 2500): draw the
  best-supported junctions only; the harmonised SV table keeps them all.
- Tests: SAVANA_PON_FILTER module test, TUMORONLY_SAVANA routing stub test, a
  RECONPLOT --max-svs case; RECONPLOT versions snapshots follow the VERSION bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Real-module run on the simulated PacBio tumour-only C8 (hg38): the PoN filter keeps
761 of 27,591 events and RECONPLOT_SAVANA finishes in 21 min with every figure
drawn (unfiltered: not finished after 2 h). At --max-svs 2500 the genome-wide panel
overflowed R's node stack, as it already does for BL1's 2,084 SAVANA junctions, so
the default drops to 1000. The 761 survivors are still germline by C8's matched
normal; the docs now say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

nf-core pipelines lint overall result: Passed ✅ ⚠️

Posted for pipeline commit 0ef8ad9

+| ✅ 234 tests passed       |+
#| ❔  22 tests were ignored |#
#| ❔   1 tests had warnings |#
!| ❗  46 tests had warnings |!
Details

❗ Test warnings:

  • nextflow_config - Config manifest.version should end in dev: 1.1.0
  • pipeline_todos - TODO string in lint_log.txt: Named file extensions MUST be emitted for ALL output channels
  • pipeline_todos - TODO string in lint_log.txt: List additional required output channels/values here
  • pipeline_todos - TODO string in lint_log.txt: Named file extensions MUST be emitted for ALL output channels
  • pipeline_todos - TODO string in lint_log.txt: List additional required output channels/values here
  • pipeline_todos - TODO string in lint_log.txt: Named file extensions MUST be emitted for ALL output channels
  • pipeline_todos - TODO string in lint_log.txt: List additional required output channels/values here
  • pipeline_todos - TODO string in lint_log.txt: Named file extensions MUST be emitted for ALL output channels
  • pipeline_todos - TODO string in lint_log.txt: List additional required output channels/values here
  • pipeline_todos - TODO string in lint_log.txt: Named file extensions MUST be emitted for ALL output channels
  • pipeline_todos - TODO string in lint_log.txt: List additional required output channels/values here
  • pipeline_todos - TODO string in lint_log.txt: Named file extensions MUST be emitted for ALL output channels
  • pipeline_todos - TODO string in lint_log.txt: List additional required output channels/values here
  • pipeline_todos - TODO string in lint_log.txt: Named file extensions MUST be emitted for ALL output channels
  • pipeline_todos - TODO string in lint_log.txt: List additional required output channels/values here
  • pipeline_todos - TODO string in lint_log.txt: Named file extensions MUST be emitted for ALL output channels
  • pipeline_todos - TODO string in lint_log.txt: List additional required output channels/values here
  • pipeline_todos - TODO string in README.md: Include a figure that guides the user through the major workflow steps. Many nf-core
  • pipeline_todos - TODO string in nextflow.config: Specify your pipeline's command line flags
  • pipeline_todos - TODO string in nextflow.config: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0
  • pipeline_todos - TODO string in base.config: Check the defaults for all processes
  • pipeline_todos - TODO string in base.config: Customise requirements for specific processes.
  • pipeline_todos - TODO string in methods_description_template.yml: #Update the HTML below to your preferred methods description, e.g. add publication citation for this pipeline
  • pipeline_todos - TODO string in meta.yml: #Add a description of the module and list keywords
  • pipeline_todos - TODO string in CONTRIBUTING.md: Add any pipeline specific contribution guidelines here, such as coding styles, procedures, checklists etc.
  • pipeline_todos - TODO string in nextflow.config: Specify any additional parameters here
  • schema_description - Ungrouped param in schema: skip_modkit
  • schema_description - No description provided in schema for parameter: generate_gvcf
  • schema_description - No description provided in schema for parameter: autocorrelation
  • schema_description - No description provided in schema for parameter: vep_custom
  • schema_description - No description provided in schema for parameter: vep_custom_tbi
  • schema_description - No description provided in schema for parameter: severus_minsupport
  • schema_description - No description provided in schema for parameter: wakhan_chroms
  • local_component_structure - fiberseq.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - deepsomatic.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - dmr.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - prepare_reference_files.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - prepare_annotation.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - ecdna.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - prepare_vep_plugins.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - phasing_haplotyping.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - prepare_signatures.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - small_variant_consensus.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - prepare_aa_data_repo.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - reconplot_figures.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure
  • local_component_structure - padfoot_annotation.nf in subworkflows/local should be moved to a SUBWORKFLOW_NAME/main.nf structure

❔ Tests ignored:

  • files_exist - File is ignored: CODE_OF_CONDUCT.md
  • files_exist - File is ignored: assets/nf-core-lrsomatic_logo_light.png
  • files_exist - File is ignored: docs/images/nf-core-lrsomatic_logo_light.png
  • files_exist - File is ignored: docs/images/nf-core-lrsomatic_logo_dark.png
  • files_exist - File is ignored: .github/ISSUE_TEMPLATE/config.yml
  • files_exist - File is ignored: .github/workflows/awstest.yml
  • files_exist - File is ignored: .github/workflows/awsfulltest.yml
  • files_exist - File is ignored: .github/CONTRIBUTING.md
  • nextflow_config - Config variable ignored: manifest.name
  • nextflow_config - Config variable ignored: manifest.homePage
  • nf_test_content - 'tests/sv_annotation_compose.nf.test' checking ignored
  • files_unchanged - File ignored due to lint config: CODE_OF_CONDUCT.md
  • files_unchanged - File ignored due to lint config: .github/ISSUE_TEMPLATE/bug_report.yml
  • files_unchanged - File ignored due to lint config: .github/PULL_REQUEST_TEMPLATE.md
  • files_unchanged - File ignored due to lint config: .github/workflows/branch.yml
  • files_unchanged - File ignored due to lint config: .github/workflows/linting.yml
  • files_unchanged - File ignored due to lint config: assets/email_template.txt
  • files_unchanged - File ignored due to lint config: assets/nf-core-lrsomatic_logo_light.png
  • files_unchanged - File ignored due to lint config: docs/images/nf-core-lrsomatic_logo_light.png
  • files_unchanged - File ignored due to lint config: docs/images/nf-core-lrsomatic_logo_dark.png
  • files_unchanged - File ignored due to lint config: docs/README.md
  • actions_awstest - 'awstest.yml' workflow not found: /home/runner/work/lrsomatic/lrsomatic/.github/workflows/awstest.yml

❔ Tests fixed:

✅ Tests passed:

Run details

  • nf-core/tools version 4.1.0
  • Run at 2026-11-02 13:18:43

@ljwharbers
ljwharbers marked this pull request as ready for review November 2, 2026 12:56
Comment thread assets/reconplot/run_reconplot.R Outdated

## after the tables, which keep every junction
n_sv <- nrow(sv)
sv <- cap_svs(sv, args$max_svs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cap is applied once across the whole genome, before regions are chosen, not "per figure".

cap_svs() runs on the genome-wide sv table before step 3 builds region_sets. Every run then draws from that same top-N set: per-chromosome (--layout separate), genome-wide, and the --regions/--genes focus panel.

  • Per-chromosome figures: on a 27k-SV sample, each chromosome only gets its share of the genome-wide top 1000. A chromosome with 300 SVs, well under any overflow, may show only a few. Its title still says top 1000 of 27591 SVs, counts that don't describe that figure.
  • Focus panel: a user-requested region whose SVs have low support can lose all of them, even if it has only a handful.

This contradicts nextflow.config ("draw at most this many SVs per figure") and the schema description ("per ReConPlot figure"). REVIEW.md requires help text to match actual behaviour.

The overflow only hits the genome-wide panel, so the fix should be one of:

  • apply the cap per region set inside plot_region_sets, after subsetting SVs to the region, and compute the title note there; or
  • apply it only to the together/genome-wide run, and reword the docs to match.

Comment thread nextflow_schema.json
"type": "integer",
"description": "Mapping quality threshold for reads counted at a het-SNP site (SAVANA's --allele_mapq, default 5). Intended for test profiles running on downsampled data below that coverage floor."
},
"savana_pb_pon_filter": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs/usage.md is missing both new parameters.

--savana_pb_pon_filter and --reconplot_max_svs are added to the schema, but not to docs/usage.md. REVIEW.md requires new parameters in both places.

The ReConPlot parameter table at docs/usage.md:466 already lists --reconplot_min_svlen, so --reconplot_max_svs belongs next to it. The SAVANA options section needs --savana_pb_pon_filter.

Comment thread bin/savana_pon_filter.py
Comment on lines +55 to +59
pon = {}
for key, entries in rows.items():
entries.sort()
pon[key] = list(zip(*entries))
return pon

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the PoN can't be parsed, the filter silently does nothing.

read_pon skips any line that has fewer than 7 comma-separated fields or non-integer columns. It does this without counting or warning. So if a genome's pon_file differs from the expected format, every row is skipped and pon comes back empty. Examples: tab-separated, or different column order or CI columns, like the CHM13 merged_PON_1000G_ASAP_withmedianCI.tsv.gz. The task then succeeds and publishes a pon_filtered VCF identical to the input. Downstream, VEP, Padfoot and ReConPlot get the unfiltered ~28k germline calls again.

Only the hand-made GRCh38-style fixture is tested. REVIEW.md asks for CHM13 to be checked as well.

Please fail, or at least warn on stderr, when no PoN rows were parsed, or when most data lines were skipped. Also confirm that the real hg38 and CHM13 PoN files parse to a non-zero row count.

@claude

claude Bot commented Nov 2, 2026

Copy link
Copy Markdown

Automated review: 3 findings (posted inline).

  1. ReConPlot cap scope. --reconplot_max_svs is applied once, genome-wide, before regions are chosen. Per-chromosome and focus-panel figures therefore lose SVs, and their titles carry genome-wide counts. The docs say "per figure".
  2. Usage docs. --savana_pb_pon_filter and --reconplot_max_svs are missing from docs/usage.md.
  3. Silent PoN parse failure. savana_pon_filter.py silently becomes a no-op if the PoN fails to parse. Only a hand-made GRCh38-style fixture is tested; the CHM13 PoN format has not been checked.

For a human reviewer to check by running things (this review could not run the pipeline or nf-test):

  • Run bin/savana_pon_filter.py against the real hg38 PoN and the CHM13 PoN, and confirm both parse to a non-zero number of rows.
  • No PacBio tumour-only sample exists in any test samplesheet. The real SAVANA_PON_FILTER → VEP_SAVANA / Padfoot / ReConPlot path is covered only by stub routing tests, so please run a PacBio tumour-only sample, e.g. in the Slurm test array.

🤖 Generated with Claude Code

ljwharbers and others added 4 commits November 2, 2026 14:13
A panel of normals in another layout (tab-separated, other columns) used to parse to nothing and pass every germline SV through as if filtered. Exit with an error when no row parses, warn when more than 1% of data lines are skipped, and log the parsed row count. Both shipped PoNs parse fully (hg38 1,039,498 rows; CHM13 1,490,071 plus its header); the C8 output is unchanged (761 of 27,591 events kept).

Addresses review comment on PR #228.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cap ran once on the genome-wide SV table before the region sets were built, so each per-chromosome figure got only its share of the genome-wide top N, a sparse focus region could lose all of its SVs, and every title carried the genome-wide count. Count the junctions each figure shows (either breakpoint in its regions) and cap only that figure; its title says top N of M with M for that figure. Uncapped figures still get the full table, so their output is unchanged.

Addresses review comment on PR #228.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses review comment on PR #228.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant